Previous topicNext topic
Help > Data Types > Pointer Data Types >
Pointers (@)

A pointer is a variable that holds the 32-bit (4 byte) address of code or data located elsewhere in memory.  It is called a pointer because it literally points to that location.  The location pointed to is known as the target of the pointer.

Pointers represent a powerful addition to the BASIC programmer's arsenal.  The address is defined at run-time, so your program can reference any memory location as if it were a standard variable.  When a pointer is used to access a memory location, it is called "indirect addressing".

Pointers are declared using the DIM statement, and the type of the target must be specified.  The keywords PTR and POINTER are synonymous.

DIM i AS INTEGER PTR 'declares i as a pointer to an Integer

or:

DIM i AS INTEGER POINTER

The above example declares i as an Integer pointer.  Before it can be used, i must be initialized with an actual address of a variable (easily done with the VARPTR function; or STRPTR for strings).  When you assign a value to a pointer variable, you are giving it an address to use later when you wish to reference the actual target.  A pointer's name alone references the pointer variable.  A pointer's name with an at sign (@) prefix, references the pointer's target:

DIM Ptr1 AS BYTE PTR ' declares Ptr1 as a byte pointer

DIM Ptr2 AS BYTE PTR ' declares Ptr2 as a byte pointer

DIM Byte1 AS BYTE    ' Declares Byte1 as a byte variable

DIM Byte2 AS BYTE    ' Declares Byte2 as a byte variable

Ptr1 = VARPTR(Byte1) ' Ptr1 points to Byte1

@Ptr1 = 36           ' Sets Byte1 to the value 36

Ptr2 = VARPTR(Byte2) ' Ptr2 points to Byte2

@Ptr2 = @Ptr1 + 4    ' Sets Byte2 to 40 (36 + 4)

In summary, when you reference a pointer variable without an at-sign, you are referencing the 32-bit address contained in it.  When you precede the name with an at-sign, you are referencing the target data located at the address "pointed to" by the pointer.

By assigning the address of another pointer to a pointer, we can set up another level of indirection. Pointers to pointers are useful when setting up linked lists in memory.  You can then access the target by adding a second at-sign in front of the pointer's name:

DIM y AS STRING POINTER

DIM z AS STRING POINTER

DIM TmpStr AS STRING

y = VARPTR(TmpStr)  ' y points to TmpStr

z = VARPTR(y)       ' z points to y

@y = "A"            ' put an "A" in TmpStr

@@z = "B"           ' overwrite it with a "B"

Display @y          ' display the target value of y

PowerBASIC supports up to 200 levels of indirection.  For each level, you add another preceding at-sign to the pointer name.  You can only use the (@) prefix with pointer variables.

A pointer with a value of zero (0) is considered a null-pointer by PowerBASIC.  Windows will generate a General Protection Fault (GPF) if you attempt to access data at an invalid pointer address.  See the section on assembler programming for more information.

The true power of pointers resides in their speed and flexibility.  Traditionally, to access memory, a BASIC programmer had to use combinations of PEEK and POKE.  This allowed the programmer to address memory as bytes.  If the target data took any other form, conversion was necessary.  Pointers allow you to address the target data in any fashion you desire, even as a user-defined structure.  Moreover, because the setup of calling PEEK and POKE is no longer necessary, access is much faster.

Let's say that we want to scan all the characters in a buffer, replacing all upper case "A"s with lower case "a"s.  The code might look something like this:

SUB Lower(zStr AS STRING)

 DIM s AS BYTE PTR, ix AS INTEGER

 s = STRPTR(zStr) ' Access the dynamic string directly

 FOR ix = 1 TO LEN(zStr)

   IF @s = 65 THEN @s = 97  ' "A" -> "a"

   INCR s

 NEXT

END SUB

When using a pointer to a structure, the prefix is placed before the structure name when you wish to access an element of the structure.  The structure name by itself refers to its address.  This distinction is extremely important when treating structures as a whole.  The following example shows two ways of doing a simple bubble sort of an array of User-Defined Types.  The first uses conventional BASIC methods, the second uses pointers to illustrate their speed and efficiency.

 

'-- Example 1 --------------

#COMPILE EXE

#DIM ALL

TYPE NameRec

 Last   AS STRING * 20   ' Last name

 First  AS STRING * 20   ' First name

END TYPE

FUNCTION PBMAIN () AS LONG

   DIM Rec(1 TO 10) AS NameRec

   DIM RP AS NameRec POINTER

   DIM ix AS LONG, ij AS LONG

   DIM hFile AS DWORD

   '-- Put some data in the records --

   FOR ix = 1 TO 10

       Rec(ix).First = CHOOSE$(ix,"Jacob","Michael","Joshua","Matthew","Ethan", _

                               "Emily","Emma","Madison","Abigail","Olivia")

       Rec(ix).Last  = CHOOSE$(ix,"SMITH","JOHNSON","WILLIAMS","JONES","BROWN", _

                               "DAVIS","MILLER","WILSON","MOORE","TAYLOR")

   NEXT ix

   '-- Sort UDT array in ascending order using a bubble sort

   '-- ARRAY SORT Rec(),FROM 1 TO 20,ASCEND  will do this as well

   FOR ix = 9 TO 1 STEP -1

       FOR ij = 1 TO ix

           IF Rec(ij-1).Last > Rec(ij).Last THEN

               SWAP Rec(ij-1), rec(ij)

           END IF

       NEXT ij

   NEXT ix

  #IF %DEF(%PB_CC32)

       FOR ix = 1 TO 10

           PRINT TRIM$(Rec(ix).Last)+ ", " +TRIM$(Rec(ix).First)

       NEXT ix

       PRINT

       PRINT "Press any key to quit ... "

       WAITKEY$

  #ELSE

      DIM msg AS STRING

      FOR ix = 1 TO 10

          msg = msg + TRIM$(Rec(ix).Last)+ ", " +TRIM$(Rec(ix).First) + $CRLF

          MSGBOX msg

      NEXT ix

  #ENDIF

END FUNCTION

 

'-- Example 2 --------------

' The difference between example 1 and this example is

' that we're manipulating pointers (4 bytes) instead

' of whole records (40 bytes).

#COMPILE EXE

#DIM ALL

TYPE NameRec

 Last   AS STRING * 20   ' Last name

 First  AS STRING * 20   ' First name

END TYPE

FUNCTION PBMAIN () AS LONG

   DIM Rec(1 TO 10) AS NameRec

   DIM RP AS NameRec POINTER

   DIM ix AS LONG, ij AS LONG

   DIM hFile AS DWORD

 

   '-- Put some data in the records --

   FOR ix = 1 TO 10

       Rec(ix).First = CHOOSE$(ix,"Jacob","Michael","Joshua","Matthew","Ethan", _

                               "Emily","Emma","Madison","Abigail","Olivia")

       Rec(ix).Last  = CHOOSE$(ix,"SMITH","JOHNSON","WILLIAMS","JONES","BROWN",

_

                               "DAVIS","MILLER","WILSON","MOORE","TAYLOR")

   NEXT ix

   '-- Sort UDT array in ascending order using a bubble sort with pointers

   '-- note a bubble sort is not recommended for large collections

   '-- and note ARRAY SORT Rec(),FROM 1 TO 20,ASCEND  will do this as well

   '-- so this is only to show pointers to UDT arrays in action!

   RP = VARPTR(Rec(1))

   FOR ix = 9 TO 1 STEP -1

       FOR ij = 1 TO ix

           'note pointers to array elements use zero based subscripts in brackets!

           IF @RP[ij-1].Last > @RP[ij].Last THEN

               SWAP @RP[ij-1], @RP[ij]

           END IF

       NEXT ij

   NEXT ix

  #IF %DEF(%PB_CC32)

       FOR ix = 1 TO 10

           PRINT TRIM$(Rec(ix).Last)+ ", " +TRIM$(Rec(ix).First)

       NEXT ix

       PRINT

       PRINT "Press any key to quit ... "

       WAITKEY$

  #ELSE

      DIM msg AS STRING

      FOR ix = 1 TO 10

          msg = msg + TRIM$(Rec(ix).Last)+ ", " +TRIM$(Rec(ix).First) + $CRLF

          MSGBOX msg

      NEXT ix

  #ENDIF

END FUNCTION

If you declare a member of a structure as a pointer, the @ prefix is used with the member name, not the structure name.  The previous example could be improved by adding a couple of pointers to the structure to point to the previous and next record, respectively.  This lets you allocate memory for a record only when needed, instead of pre-allocating a fixed-size array of records.  The modified structure would look something like this:

TYPE NameRec

 Last AS STRING * 20   ' Last name

 First AS STRING * 20  ' First name

 Nxt AS NameRec PTR    ' Pointer to next record

 Prv AS NameRec PTR    ' Pointer to previous record

END TYPE

DIM Rec AS NameRec

The pointer members are then accessed like this:

Rec.@Nxt   ' next record

Rec.@Prv   ' previous record

Putting the @ prefix in front of the structure name (i.e., @Rec) would cause a compile-time error, as Rec itself is not a pointer.

When calculating the length of the Type, all pointers are internally stored as Double-word (DWORD) variables, so NameRec is 48 bytes long (20 + 20 + 4 + 4).  If you need to know the length of a Type, it is easier to let PowerBASIC calculate it for you using the LEN function than doing it yourself:

length = LEN(structure)

 

See Also

Pointers to nul-terminated and fixed-length strings

Pointers to arrays

Pointers to arrays with dual indexes